package com.media.entrance.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.media.entrance.controller.CustomerController;
import com.media.entrance.model.MatchAndResourceModel;
import com.media.entrance.model.MediaResourceModel;
import com.media.entrance.utils.MediaEntranceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class MediaResourceManageService {
    @NacosInjected
    private NamingService namingService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisService redisService;

    public void getMediaServiceResource(MatchAndResourceModel matchInfo) {
        String roomId = matchInfo.getRoomId();

        String redisResult = redisService.getValue(roomId);
        log.info("getMediaServiceResource get redis roomId: [" +  roomId + "]:redisResult:" +redisResult);

        // 找一个转发服务，如果转发需要更新则更新
        MediaResourceModel forwarderInfo = addServiceInfo("forwarderInfo", "forwarder-service", 5, redisResult);
        // 找一个录像服务，如果录像需要更新则更新
        MediaResourceModel recorderInfo = addServiceInfo("recorderInfo", "recorder-service", 5, redisResult);
        try {
            if (forwarderInfo != null) {
                matchInfo.setForwarderInfo(forwarderInfo);
            }
            if (recorderInfo != null) {
                matchInfo.setRecorderInfo(recorderInfo);
            }
            String result = new ObjectMapper().writeValueAsString(matchInfo);
            redisService.setValue(roomId, result, 0);
            log.info("getMediaServiceResource return result : [" +  result + "]");
        } catch (JsonProcessingException e) {
            log.error("getMediaServiceResource throw error" + matchInfo, e);
        }
    }

    // 处理获取新的服务信息 或从redis里取旧的信息
    private MediaResourceModel addServiceInfo(String tagKey, String serviceName, int redisKeepTimeout, String redisResult) {
        if (redisResult != null) {
            try {
                MatchAndResourceModel redisObject = new ObjectMapper().readValue(redisResult, MatchAndResourceModel.class);
                log.info("addServiceInfo get redis before " + serviceName + ": [" +  redisObject+ "]");
                MediaResourceModel mediaResourceModel = processRedistribute(redisObject, tagKey, serviceName, redisKeepTimeout);
                log.info("addServiceInfo get redis after " + serviceName + ": [" +  mediaResourceModel + "]");
                return mediaResourceModel;
            } catch (JsonProcessingException e) {
                log.error("addServiceInfo redis change to object fail: " + redisResult);
                throw new RuntimeException(e);
            }
        } else {
            MediaResourceModel mediaResourceModel = processFirstDistibute(tagKey, serviceName, redisKeepTimeout);
            log.info("addServiceInfo get new " + serviceName + ": [" + mediaResourceModel + "]");
            return mediaResourceModel;
        }
    }

    public MediaResourceModel processRedistribute(MatchAndResourceModel redisResult, String tagName, String serviceName, int timeout) {
        MediaResourceModel mediaResourceModel;
        if (tagName.equals("recorderInfo")) {
            mediaResourceModel = redisResult.getRecorderInfo();
        } else {
            mediaResourceModel = redisResult.getForwarderInfo();
        }
        if (mediaResourceModel == null) {
            return null;
        }
        List<Instance> serviceInfo = getNacosHealthServiceInfo(serviceName, true);
        String url = mediaResourceModel.getServerUrl();
        if (isUrlInNacosInstance(serviceInfo, url)) {
            log.info("[processRedistribute]No need to redistribute:[" + mediaResourceModel + "]!");
            return mediaResourceModel;
        } else {
            log.info("[processRedistribute]Need to redistribute:[" + mediaResourceModel + "]!");
            // 使用上次的nacos结果获取临时存储
            JSONObject redisRecord = processNacosInstanceGetAllServiceInfo(serviceInfo, tagName);
            redisService.setValue(tagName, redisRecord.toJSONString(), timeout);
            log.info("[processRedistribute]temp result:[" + redisRecord.toJSONString() + "]!");
            // 返回结果
            mediaResourceModel = processServerInfo(redisRecord, tagName);
            log.info("[processRedistribute]get nacos total result :[" + redisRecord.toJSONString() + "]");
            return mediaResourceModel;
        }
    }

    // 处理nacos中返回的是否包含制定的IP PORT
    public boolean isUrlInNacosInstance(List<Instance> nacosInstance, String url) {
        if (nacosInstance.size() == 0) {
            log.error("nacos get no health service!");
            return false;
        }
        for (Instance instance : nacosInstance) {
            String instanceUri = instance.getIp() + ":" + instance.getPort();
            if (instanceUri.equals(url)) {
                return true;
            }
        }
        return false;
    }
    // 获取nacos中的的servicename的实例，便于分配
    public List<Instance> getNacosHealthServiceInfo(String serviceName, boolean isHealth) {
        List<Instance> sfusInfo;
        try {
            sfusInfo = namingService.selectInstances(serviceName, isHealth);
            log.info("getNacosHealthServiceInfo get nacos healthStatus:[" +isHealth + "service:[" + serviceName + "] return : [" +  sfusInfo.toString() + "]");
            return sfusInfo;
        } catch (NacosException e) {
            log.error("getNacosHealthServiceInfo get NacosException error: [" +  e + "]");
            return new ArrayList<>();
        }
    }

    public MediaResourceModel processFirstDistibute(final String type, final String serviceName, final int timeout) {
        String redisResult = redisService.getValue(type);
        // 获取redis缓存的查询数据
        if (redisResult != null) {
            JSONObject redisInfo = JSONObject.parseObject(redisResult);
            log.info("processFirstDistibute get direct redisInfo return : [" +  redisInfo.toJSONString() + "]");
            MediaResourceModel resultInfo = processServerInfo(redisInfo, type);
            log.info("processFirstDistibute get MediaResourceModel : [" +  resultInfo + "]");
            return resultInfo;
        } else {
            // 通过nacos获取所有健康的实例信息
            List<Instance> sfusInfo = getNacosHealthServiceInfo(serviceName, true);
            JSONObject redisRecord = processNacosInstanceGetAllServiceInfo(sfusInfo,type);
            // 更新到redis中
            redisService.setValue(type, redisRecord.toJSONString(), timeout);
            // 返回结果
            log.info("processFirstDistibute get nacos total result :[" +  redisRecord.toJSONString() + "]");
            if (redisRecord.isEmpty()) {
                return null;
            } else {
                MediaResourceModel resultInfo = processServerInfo(redisRecord, type);
                log.info("processFirstDistibute get MediaResourceModel : [" +  resultInfo + "]");
                return resultInfo;
            }
        }
    }

    private JSONObject processNacosInstanceGetAllServiceInfo(List<Instance> sfusInfo, String type) {// 如果没有健康的服务实例
        if (sfusInfo.size() == 0) {
            return new JSONObject();
        }
        JSONObject redisRecord = new JSONObject();
        redisRecord.put(type, new JSONArray());
        for (Instance instance : sfusInfo) {
            // 请求每个转发服务器上的信息
            String uri = "http://" + instance.getIp() + ":" + instance.getPort() + "/media/server/" + type;
            System.out.println("get uri info " + uri);
            HttpHeaders httpHeaders = new HttpHeaders();
            MediaType mediaType = MediaType.parseMediaType("application/json;charset=UTF-8");
            httpHeaders.setContentType(mediaType);
            HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(httpHeaders);
            ResponseEntity<String> apiResponse = restTemplate.exchange(uri, HttpMethod.GET, formEntity, String.class);

            // 构造数据
            JSONObject getResult = JSONObject.parseObject(apiResponse.getBody());
            log.info("processNacosInstanceGetAllServiceInfo get nacos single result :[" + uri + "] return : [" +  getResult.toJSONString() + "]");
            getResult.put("serverUrl", instance.getIp() + ":" + instance.getPort());
            redisRecord.getJSONArray(type).add(getResult);
        }
        return redisRecord;
    }


    public void releaseMediaServiceResource(String roomId) {
        log.info("releaseMediaResource recevie roomId: " + roomId);
        if (roomId == null) {
            throw new MediaEntranceException("deviceId or roomId is null");
        }
        Boolean result = false;
        String redisResult = redisService.getValue(roomId);
        log.info("releaseMediaResource getRedisResult receive : [" + roomId + "]:" + redisResult);
        if (redisResult != null) {
            redisService.clearValue(roomId);
        }
    }

    // 获取jsonarray中的innerKeyName的最小值的索引号
    private String getMinKeyInJSONObject(JSONObject inputInfo){
        int minIndexTotal = Integer.MAX_VALUE;
        String minKey = "";
        HashMap<String, Integer> countMap = JsonObjectToHashMap(inputInfo);
        for (String key: countMap.keySet()) {
            if (countMap.get(key) < minIndexTotal) {
                minIndexTotal = countMap.get(key);
                minKey = key;
            }
        }
        return minKey;
    }
    // 转换jsonobject to hashmap
    private HashMap<String, Integer> JsonObjectToHashMap(JSONObject jsonObj){
        HashMap<String, Integer> data = new HashMap<>();
        for (String s : jsonObj.keySet()) {
            Integer value = jsonObj.getIntValue(s);
            data.put(s, value);
        }
        return data;
    }

    // 获取数组中的最小值的index
    private int getMinInArray(JSONArray inputInfo){
        int minIndexTotal = Integer.MAX_VALUE;
        int recordIndex = -1;
        for (int i = 0; i < inputInfo.size(); i++) {
            int count = inputInfo.getJSONObject(i).getIntValue("totalNumber");
            if (count < minIndexTotal) {
                minIndexTotal = count;
                recordIndex = i;
            }
        }
        return recordIndex;
    }

    // 处理返回的数据
    private MediaResourceModel processServerInfo(JSONObject inputInfo, String type){
        //获取所有的转发信息，找到总房间数最少的转发url
        JSONArray tempTotal = inputInfo.getJSONArray(type);
        int recordIndex = getMinInArray(tempTotal);

        //在总房间数最少的转发里找每个CPU核心上最少的房间索引
        JSONObject tempWorkers = tempTotal.getJSONObject(recordIndex).getJSONObject("everyCoreInfo");
        String coreId = getMinKeyInJSONObject(tempWorkers);

        // 将结果放到result中返回
        String serverUrl = tempTotal.getJSONObject(recordIndex).getString("serverUrl");
        return new MediaResourceModel(coreId, serverUrl);
    }
}
